package org.soc.timesheet.custom.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.mail.internet.MimeMessage;
import javax.persistence.TypedQuery;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.crypto.CryptoException;
import org.cheetah.encrypt.Encryptor;
import org.hibernate.annotations.Source;
import org.soc.timesheet.custom.annotation.AOPLogger;
import org.soc.timesheet.custom.annotation.AOPLogger.LoggerLevel;
import org.soc.timesheet.custom.bean.LoginBean;
import org.soc.timesheet.custom.bean.LoginSuccess;
import org.soc.timesheet.custom.bean.PasswordBean;
import org.soc.timesheet.domain.CustomersEnabled;
import org.soc.timesheet.domain.Emails;
import org.soc.timesheet.domain.Functions;
import org.soc.timesheet.domain.Roles;
import org.soc.timesheet.domain.TokenType;
import org.soc.timesheet.domain.Users;
import org.soc.timesheet.domain.UsersLogged;
import org.soc.timesheet.server.domain.service.CustomersEnabledService;
import org.soc.timesheet.server.domain.service.FunctionsService;
import org.soc.timesheet.server.domain.service.TokenTypeService;
import org.soc.timesheet.server.domain.service.UsersLoggedService;
import org.soc.timesheet.server.domain.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Controller
@RequestMapping("/auth")
public class LoginController {

	@Autowired
	CustomersEnabledService customersEnableService;

	@Autowired
	UsersLoggedService usersLoggedService;

	@Autowired
	@Qualifier(value = "mailSender")
	JavaMailSender mailSender;

	@Autowired
	TokenTypeService tokenTypeService;

	@Autowired
	UsersService usersService;

	@Autowired
	FunctionsService functionsService;

	@Value("${email.from}")
	String from;

	Logger log = Logger.getLogger(LoginController.class);
	private Map<String, String> sessionKeyMap = new HashMap<String, String>();

	@RequestMapping(value = "/login", method = RequestMethod.POST)
	@ResponseBody
	public ResponseEntity<String> login(
			@RequestHeader(value = "client-key", required = true) String headerClientKey,
			@RequestHeader(value = "session-id", required = true) String headerSessionId) {
		CustomersEnabled ce = CustomersEnabled
				.findCustomersEnabledsByCustomerKeyEquals(headerClientKey)
				.getSingleResult();
		if (ce != null) {
			sessionKeyMap.put(headerSessionId, headerClientKey);

			return new ResponseEntity<String>(ce.getCustomerKey(),
					HttpStatus.OK);
		}
		return new ResponseEntity<String>("NO_KEY", HttpStatus.NO_CONTENT);
	}

	@RequestMapping(value = "/loginenc", method = RequestMethod.POST, headers = {
			"Content-Type=application/octet-stream", "Accept=application/json" })
	@ResponseBody
	@AOPLogger(name = "controller.LOGIN", loggerLevel = { LoggerLevel.DEBUG,
			LoggerLevel.INFO })
	public ResponseEntity<String> loginEncrypted(
			@RequestHeader(value = "session-id") String headerSessionId,
			@RequestBody byte[] data) throws CryptoException,
			JsonParseException, JsonMappingException, IOException {
		HttpHeaders header = new HttpHeaders();
		header.add("Content-Type", "application/json");
		String clientKey = sessionKeyMap.get(headerSessionId);
		// ottenuto il client key decripto i dati inviati dal client
		Encryptor encryptor = new Encryptor(clientKey);
		String json = encryptor.decryptString(data);
		ObjectMapper jsonMapper = new ObjectMapper();
		LoginBean lb = jsonMapper.readValue(IOUtils.toInputStream(json),
				LoginBean.class);
		TypedQuery<Users> tqUsers = Users.findUsersesByLoginEquals(lb
				.getUsername());
		// TODO Auto-generated catch block
		tqUsers.setMaxResults(1);// trattandosi di login mi aspetto un unico
									// record
		List<Users> users = tqUsers.getResultList();
		if (users == null || users.size() == 0) {
			return new ResponseEntity<String>(
					"{\"error\": \"Utente non tovato\" }", header,
					HttpStatus.OK);
		}
		Users user = users.get(0);
		byte[] sysPassword = user.getPassword();// la password è criptata nel
												// momento della creazione
												// utilizzando la clientKey come
												// chiave di criptazione ora va
												// decriptata e confrontata con
												// quella inserita dall'utente
		if (!encryptor.decryptString(sysPassword).equals(lb.getPassword())) {
			return new ResponseEntity<String>(
					"{\"error\": \"Password errata\" }", header, HttpStatus.OK);
		}
		LoginSuccess ls = createLoginSuccess(user);
		log.debug("creato token: " + ls.getToken());
		TokenType tokenType = tokenTypeService.findByTypeDesc("SESSION");

		// memorizzo l'utente loggato sulla base dati. Da ora in poi la
		// comunicazione tra client e server avverrà attraverso il token.
		UsersLogged usersLogged = new UsersLogged();
		usersLogged.setIdTokenType(tokenType.getId());
		usersLogged.setIdUser(user);
		usersLogged.setId(RandomStringUtils.randomAlphanumeric(100));
		usersLogged.setLastAccess(GregorianCalendar.getInstance());
		usersLogged.setSessioId(headerSessionId);
		usersLogged.setToken(ls.getToken());
		usersLoggedService.saveUsersLogged(usersLogged);
		System.out.println("=============================================");
		System.out.println(jsonMapper.writeValueAsString(ls));
		System.out.println("=============================================");

		return new ResponseEntity<String>(jsonMapper.writeValueAsString(ls),
				header, HttpStatus.OK);
	}

	private LoginSuccess createLoginSuccess(Users user) {
		LoginSuccess ls = new LoginSuccess();
		ls.setToken(RandomStringUtils.randomAlphanumeric(50));
		List<Roles> roles = new ArrayList<Roles>();
		roles.add(user.getIdRole());
		List<Functions> functions = functionsService.findByRolesesIn(roles);
		ls.setFunctions(functions);
		return ls;
	}

	/**
	 * Per il cambio password viene passato il token, da cui si ricava la
	 * client-key per la decriptazione della password. Il token una volta
	 * avvenuto il cambio password viene eliminato dalla memoria. Viene inviata
	 * una mail con la conferma dell'avenuto cambio di password. Al client viene
	 * restituito il LoginSuccess.
	 * 
	 * @param aToken
	 * @return
	 * @throws CryptoException
	 */
	@RequestMapping(value = "/recover/{token}", method = RequestMethod.POST, headers = {
			"Content-Type=application/octet-stream", "Accept=application/json" })
	@ResponseBody
	@AOPLogger(name = "controller.LOGIN", loggerLevel = { LoggerLevel.DEBUG,
			LoggerLevel.INFO })
	public ResponseEntity<String> changePassword(
			@PathVariable("token") String aToken, @RequestBody byte[] data)
			throws Exception {
		UsersLogged tempUsersLogged = UsersLogged.findUsersLoggedsByTokenEquals(
				aToken).getSingleResult();
		List<TokenType> tokenTypes = tokenTypeService.findAllTokenTypes();
		
		if(tempUsersLogged==null){
			new ResponseEntity<String>(
					"{\"error\": \"Token non valido\" }",
					HttpStatus.OK);
		}
		String clientKey = tempUsersLogged.getIdUser().getIdCustomer()
				.getCustomerKey();
		Encryptor encryptor = new Encryptor(clientKey);
		String jsonDecript = encryptor.decryptString(data);
		// verifico se le due password inserite corrispondono
		PasswordBean pb = new ObjectMapper().readValue(jsonDecript,
				new TypeReference<PasswordBean>() {
				});
		if (pb.getRepeatPassword() == null || pb.getNewPassword() == null
				|| pb.getRepeatPassword().trim().equals("")
				|| pb.getNewPassword().trim().equals("")
				|| !pb.getRepeatPassword().equals(pb.getNewPassword())) {
			new ResponseEntity<String>(
					"{\"error\": \"Le password non corrispondono o non seguono le regole di sicurezza.\n\rLa password non puo' essere vuota\" }",
					HttpStatus.OK);
		}
		Users u = tempUsersLogged.getIdUser();
		u.setPassword(encryptor.encrypt(pb.getNewPassword().getBytes()));
		usersService.saveUsers(u);
		LoginSuccess ls = createLoginSuccess(u);
		UsersLogged nUsersLogged = new UsersLogged();
		nUsersLogged.setIdTokenType(tokenTypeService.findByTypeDesc("SESSION").getId());
		nUsersLogged.setToken(ls.getToken());
		nUsersLogged.setLastAccess(Calendar.getInstance());
		nUsersLogged.setIdUser(u);
		nUsersLogged.setSessioId(RequestContextHolder.currentRequestAttributes().getSessionId());
		
		usersLoggedService.saveUsersLogged(nUsersLogged);
		usersLoggedService.deleteUsersLogged(tempUsersLogged);
		HttpHeaders header = new HttpHeaders();
		header.add("Content-Type", "application/json");
		return new ResponseEntity<String>(new ObjectMapper().writeValueAsString(ls),
				header, HttpStatus.OK);
	}

	/**
	 */
	@RequestMapping(value = "/recover/{username}", method = RequestMethod.GET)
	@ResponseBody
	@AOPLogger(name = "controller.LOGIN", loggerLevel = { LoggerLevel.DEBUG,
			LoggerLevel.INFO })
	public ResponseEntity<String> recover(
			@PathVariable("username") String username,
			@RequestHeader(value = "url-recover") String urlRecover)
			throws CryptoException, JsonParseException, JsonMappingException,
			IOException {
		Users user = usersService.findByLogin(username);
		if (user == null) {
			return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
		}
		String token = RandomStringUtils.randomAlphanumeric(50);
		urlRecover += "?tkn=" + token;
		StringBuilder message = new StringBuilder(
				"E' stato richiesto il recupero della password. Pre farlo cliccare sul seguente link.\n\r")
				.append(urlRecover);
		SimpleMailMessage smm = new SimpleMailMessage();
		Set<Emails> emails = user.getEmailss();
		Stream<String> stream = emails.stream().map(email -> email.getEmail());
		String[] to = stream.toArray(size -> new String[size]);

		smm.setText(message.toString());
		smm.setTo(to);
		smm.setFrom(from);
		mailSender.send(smm);
		UsersLogged logged = new UsersLogged();
		logged.setId(RandomStringUtils.randomAlphanumeric(100));
		logged.setIdTokenType(tokenTypeService.findByTypeDesc("RECOVER")
				.getId());
		logged.setIdUser(user);
		logged.setSessioId("RECOVER_"
				+ RandomStringUtils.randomAlphanumeric(30));
		logged.setLastAccess(Calendar.getInstance());
		logged.setToken(token);
		this.usersLoggedService.saveUsersLogged(logged);
		return null;

	}

}
